package com.example.demo.game.plane;

import com.example.demo.controller.game.PlaneCmd;
import com.example.demo.entity.User;
import com.example.demo.game.GameMgrInterface;
import com.example.demo.runner.code.RunnerMessage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;


public class PlaneGameMgr implements GameMgrInterface {

    final double DELTA_TIME = 0.2; // 每一步的时间
    final int STEPS = 10; // 总共有多少步
    final double FRC_K = 0.2; // 摩擦系数
    final double POT_RADIAS = 500; //小飞机半径
    final int HEIGHT = 9000;
    final int WEIGHT = 16000;
    private final Map<User, List<Integer>> userIndexListMap; // TODO this is ugly. refactor this
    private final List<User> uniqueUsersList;
    // 存了所有飞机、检查点相关的信息
    Pot pots[];
    Vec checkPoints[];

    private int turns = 0;

    /**
     * @param count number of checkpoints
     * @param users repeatable users, each element for one plane
     */
    public PlaneGameMgr(int count, List<User> users) {
        generateCheckPoints(count);

        int userNum = users.size();
        userIndexListMap = new HashMap<>(users.size());
        pots = new Pot[userNum];
        for (int i = 0; i < userNum; i++) {
            pots[i] = new Pot(users.get(i));
            pots[i].pos = checkPoints[0].add(new Vec(100, 100).mul(i));

            userIndexListMap.putIfAbsent(users.get(i), new ArrayList<>());
            userIndexListMap.get(users.get(i)).add(i);
        }
        uniqueUsersList = new ArrayList<>(userIndexListMap.keySet());
    }

    private Vec[] generateCheckPoints(int count) {
        Random random = new Random(System.currentTimeMillis());
        List<Vec> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            list.add(new Vec(random.nextInt(WEIGHT), random.nextInt(HEIGHT)));
        }
        checkPoints = list.toArray(new Vec[0]);
        return checkPoints;
    }

    @Override
    public String parseAction(User user, BlockingQueue<RunnerMessage> raw) {
        int nPlane = userIndexListMap.get(user).size(); // number of planes
        StringBuilder action = new StringBuilder();
        for (int i = 0; i < nPlane; i++) {
            try {
                action.append(raw.take().getStrMsg());
                action.append(' ');
                action.append(raw.take().getStrMsg());
                action.append(' ');
                action.append(raw.take().getStrMsg());
                action.append(' ');
            } catch (InterruptedException e) {
                // TODO what about time out?
                e.printStackTrace();
            }
        }
        return action.toString();
    }

    @Override
    public Map getCanvas() {
        Map<String, Object> result = new HashMap<>(2);
        Map<Integer, List<List<Double>>> planesState = new HashMap<>(uniqueUsersList.size());
        for (Map.Entry<User, List<Integer>> entry : userIndexListMap.entrySet()
        ) {
            User user = entry.getKey();
            List<Integer> planeList = entry.getValue();
            List<List<Double>> list = planeList.stream()
                .map(v -> Arrays.asList(pots[v].pos.x, pots[v].pos.y, pots[v].rot.rad()
                ))
                .collect(Collectors.toList());

            planesState.put(user.getId(), list);
        }
        for(Pot pot : pots){
            System.out.println("visit "+pot.visited.toString());
        }
        result.put("pots", planesState);
        result.put("turns", turns);
        return result;
    }

    public String step(Map<User, String> actions) throws Exception {
        List<PlaneCmd> cmds = new ArrayList<>(pots.length);
        for (int i = 0; i < pots.length; i++) {
            // TODO ugly, refactor later
            cmds.add(new PlaneCmd(0, 0, "placeholder"));
        }
        for (Map.Entry<User, String> entry : actions.entrySet()) {
            User user = entry.getKey();
            String cmd = entry.getValue();
            String inputs[] = cmd.replace('\n', ' ').split(" ");
            if (inputs.length % 3 != 0) {
                // TODO
                throw new Exception("ERROR the length of inputs is " + inputs.length);
            }
            for (int i = 0; i < inputs.length; i += 3) {
                cmds.set(userIndexListMap.get(user).get(i),
                    new PlaneCmd(inputs[i], inputs[i + 1], inputs[i + 2]));
            }
        }
        turns += 1;
        return play(cmds);
    }

    @Override
    public boolean isDone() {
        // TODO remove it
        if (turns >= 10) {
            return true;
        }

        // TODO
        //   check whether all checkpoints are arrived by particular plane 3 times
        int nCkpt = checkPoints.length;
        Arrays.stream(pots).map(pot -> {
            List<Integer> vis = pot.visited;
            vis.sort(Integer::compareTo);
            int cur = 0;
            int cnt = 0;
            for (int i = 0; i < nCkpt; ++i) {
                cnt = 0;

                if (cur >= vis.size()) {
                    break;
                }
                while (vis.get(cur) == i) {
                    ++cnt;
                    ++cur;
                    if (cur >= vis.size()) {
                        break;
                    }
                }

                if (cnt < 3) {
                    return false;
                }

            }
            return true;
        });
        return false;
    }

    @Override
    public List<User> getUsers() {
        return uniqueUsersList;
    }

    private String play(List<PlaneCmd> cmds) {
        for(int i=0;i<STEPS;i++){
            playOnStep(cmds);
        }
        return getState();
    }

    private void playOnStep(List<PlaneCmd> cmds){
        // 处理加速度、摩擦力
        for (int i = 0; i < cmds.size(); i++) {
            PlaneCmd cmd = cmds.get(i);
            Pot pot = pots[i];

//            System.out.println(cmd.toString());
            if (cmd.thrust.equals("BOOST")) {
                // TODO support BOOST
            } else {
                // 扭转机头，确定飞机角度


                // 推力 = （目标位置 - 飞船位置）.单位化()*油门大小
                Vec force;
                if(new Vec(cmd.x, cmd.y).sub(pot.pos).len() == 0){
                    force = new Vec(0,0);
                }
                else {
                    force = new Vec(cmd.x, cmd.y).sub(pot.pos).normal().mul(Double.parseDouble(cmd.thrust));
                }
                // 摩擦力 = 速度的反方向* km
                Vec friction;
                if (pot.speed.len() == 0) {
                    friction = new Vec(0, 0);
                } else {
                    friction = pot.speed.normal().mul(-1 * FRC_K * pot.mass);
                }
                // 新的速度 += （推力+摩擦力)*时间
                pot.speed = pot.speed.add(force.add(friction).mul(DELTA_TIME));
                // 新的位置 += 速度*时间
                pot.pos = pot.pos.add(pot.speed.mul(DELTA_TIME));
            }
        }
        // 处理碰撞
        for (int i = 0; i < pots.length; i++) {
            for (int j = i + 1; j < pots.length; j++) {
                Pot a = pots[i];
                Pot b = pots[j];
                solveCollision(a, b);
            }
        }

        // 处理检查点
        for (Pot pot : pots) {
            for (int i = 0; i < checkPoints.length; i++) {
                Vec point = checkPoints[i];
                if (pot.dis(point) <= 600 && pot.visited.get(pot.visited.size() - 1) != i) {
                    pot.visited.add(i);
                }
            }
        }
    }

    public String getState() {
        StringBuilder output = new StringBuilder();
        // TODO order of pots?
        for (Pot pot : pots) {
            int lastVisited = pot.visited.get(pot.visited.size() - 1);
            System.out.println("lastVisted "+lastVisited);
            Vec nextPoint = checkPoints[(lastVisited + 1) % checkPoints.length];
            output.append(String.format("%d %d %d %d \n",
                Math.round(pot.pos.x), Math.round(pot.pos.y),
                Math.round(nextPoint.x), Math.round(nextPoint.y)));
        }
        return output.toString();
    }

    private void solveCollision(Pot a, Pot b) {
        if (willCollision(a, b)) {
            switchSpeedCollision(a, b);
        }
    }

    private boolean willCollision(Pot a, Pot b) {
        if (a.dis(b) > 2 * POT_RADIAS) {
            return false;
        }
        // a.speed*(b.pos-a.pos) a的速度在ab连线上的分量长度
        double asl = a.speed.doc(b.pos.sub(a.pos));
        // b.speed*(a.pos-b.pos)
        double bsl = b.speed.doc(a.pos.sub(b.pos));
        return asl + bsl > 0;
    }

    private void switchSpeedCollision(Pot a, Pot b) {
        // a.speed*(b.pos-a.pos) a的速度在ab连线上的分量长度
        double asl = a.speed.doc(b.pos.sub(a.pos));
        // b.speed*(a.pos-b.pos)
        double bsl = b.speed.doc(a.pos.sub(b.pos));

        // 根据长度算出a.speed在ab连线上的速度分量的矢量
        Vec as = b.pos.sub(a.pos).mul(asl);
        Vec bs = a.pos.sub(b.pos).mul(bsl);

        // 计算a.speed在ab垂直方向的速度分量
        Vec at = a.speed.sub(as);
        Vec bt = b.speed.sub(bs);

        // 新的a.speed = at+bs a的水平分量+b的垂直分量
        a.speed = at.add(bs);
        b.speed = bt.add(as);
    }
}
